/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.forces.maneuvers;

import org.apache.commons.math.geometry.Rotation;
import org.apache.commons.math.geometry.Vector3D;

import ore.Attitude;
import ore.Frame;
import ore.PositionVelocity;
import ore.SpacecraftState;
import ore.errors.OrekitException;
import ore.frames.FramesFactory;
import ore.orbits.EquinoctialOrbit;
import ore.propagation.events.EventDetector;
import ore.time.AbsoluteDate;


/** 
 * Impulse maneuver model.
 * 
 * This class implements an impulse maneuver as a discrete event that
 * can be provided to any {@link ore.propagation.Propagator
 * Propagator}.
 * 
 * The maneuver is triggered when an underlying event generates a
 * {@link EventDetector#STOP STOP} event, in which case this class will
 * generate a {@link EventDetector#RESET_STATE RESET_STATE} event (the
 * stop event from the underlying object is therefore filtered out).
 * In the simple cases, the underlying event detector may be a basic
 * {@link ore.propagation.events.DateDetector date event}, but it
 * can also be a more elaborate {@link
 * ore.propagation.events.ApsideDetector apside event} for apogee
 * maneuvers for example.
 * 
 * The maneuver is defined by a single velocity increment in satellite
 * frame. The current attitude of the spacecraft, defined by the current
 * spacecraft state, will be used to compute the velocity direction in
 * inertial frame. A typical case for tangential maneuvers is to use a
 * {@link ore.attitudes.LofOffset LOF aligned} attitude law for state propagation and a
 * velocity increment along the +X satellite axis.
 * 
 * Beware that the triggering event detector must behave properly both
 * before and after maneuver. If for example a node detector is used to trigger
 * an inclination maneuver and the maneuver change the orbit to an equatorial one,
 * the node detector will fail just after the maneuver, being unable to find a
 * node on an equatorial orbit! This is a real case that has been encountered
 * during validation ...
 * 
 * @see ore.propagation.Propagator#addEventDetector(EventDetector)
 * 
 * @author Luc Maisonobe
 */
public class ImpulseManeuver
    extends Object
    implements EventDetector
{


    private final EventDetector trigger;

    /** Velocity increment in satellite frame.
     */
    private final Vector3D deltaVSat;

    /** Engine exhaust velocity.
     */
    private final double vExhaust;


    public ImpulseManeuver(EventDetector trigger, Vector3D deltaVSat,
                           double isp)
    {
        super();
        this.trigger   = trigger;
        this.deltaVSat = deltaVSat;
        this.vExhaust  = ConstantThrustManeuver.G0 * isp;
    }


    public double getMaxCheckInterval() {
        return this.trigger.getMaxCheckInterval();
    }
    public int getMaxIterationCount() {
        return this.trigger.getMaxIterationCount();
    }
    public double getThreshold() {
        return this.trigger.getThreshold();
    }
    public int eventOccurred(SpacecraftState s, boolean increasing)
        throws OrekitException
    {
        /*
         * Filter underlying event
         */
        if (STOP == this.trigger.eventOccurred(s, increasing))
            return RESET_STATE;
        else
            return CONTINUE;
    }
    public double g(SpacecraftState s) throws OrekitException {
        return this.trigger.g(s);
    }
    public SpacecraftState resetState(SpacecraftState oldState)
        throws OrekitException
    {
        Frame eme2000     = FramesFactory.getEME2000();
        AbsoluteDate date = oldState.getDate();
        Attitude attitude = oldState.getAttitude();
        /*
         * Convert velocity increment in EME2000 frame
         */
        Rotation refToEME2000 =
            attitude.getReferenceFrame().getTransformTo(eme2000, date).getRotation();
        Rotation satToEME2000 = refToEME2000.applyTo(attitude.getRotation().revert());
        Vector3D deltaV = satToEME2000.applyTo(deltaVSat);
        /*
         * Apply increment to position/velocity
         */
        PositionVelocity oldPV = oldState.getPositionVelocity(eme2000);
        PositionVelocity newPV = new PositionVelocity(oldPV.getPosition(),
                                                      oldPV.getVelocity().add(deltaV));
        /*
         * Compute new mass
         */
        double newMass = oldState.getMass() * Math.exp(-deltaV.getNorm() / vExhaust);
        /*
         * Pack everything in a new state
         */
        return new SpacecraftState(new EquinoctialOrbit(newPV, eme2000, date, oldState.getMu()),
                                   attitude, newMass);
    }

}
